{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T03:09:10.489853100Z",
     "start_time": "2024-07-18T03:09:06.122513400Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.9.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.2\n",
      "sklearn 1.5.0\n",
      "torch 2.3.1+cpu\n",
      "cpu\n"
     ]
    }
   ],
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "    \n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 准备数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T03:09:23.030487800Z",
     "start_time": "2024-07-18T03:09:22.690908200Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      ".. _california_housing_dataset:\n",
      "\n",
      "California Housing dataset\n",
      "--------------------------\n",
      "\n",
      "**Data Set Characteristics:**\n",
      "\n",
      ":Number of Instances: 20640\n",
      "\n",
      ":Number of Attributes: 8 numeric, predictive attributes and the target\n",
      "\n",
      ":Attribute Information:\n",
      "    - MedInc        median income in block group\n",
      "    - HouseAge      median house age in block group\n",
      "    - AveRooms      average number of rooms per household\n",
      "    - AveBedrms     average number of bedrooms per household\n",
      "    - Population    block group population\n",
      "    - AveOccup      average number of household members\n",
      "    - Latitude      block group latitude\n",
      "    - Longitude     block group longitude\n",
      "\n",
      ":Missing Attribute Values: None\n",
      "\n",
      "This dataset was obtained from the StatLib repository.\n",
      "https://www.dcc.fc.up.pt/~ltorgo/Regression/cal_housing.html\n",
      "\n",
      "The target variable is the median house value for California districts,\n",
      "expressed in hundreds of thousands of dollars ($100,000).\n",
      "\n",
      "This dataset was derived from the 1990 U.S. census, using one row per census\n",
      "block group. A block group is the smallest geographical unit for which the U.S.\n",
      "Census Bureau publishes sample data (a block group typically has a population\n",
      "of 600 to 3,000 people).\n",
      "\n",
      "A household is a group of people residing within a home. Since the average\n",
      "number of rooms and bedrooms in this dataset are provided per household, these\n",
      "columns may take surprisingly large values for block groups with few households\n",
      "and many empty houses, such as vacation resorts.\n",
      "\n",
      "It can be downloaded/loaded using the\n",
      ":func:`sklearn.datasets.fetch_california_housing` function.\n",
      "\n",
      ".. topic:: References\n",
      "\n",
      "    - Pace, R. Kelley and Ronald Barry, Sparse Spatial Autoregressions,\n",
      "      Statistics and Probability Letters, 33 (1997) 291-297\n",
      "\n",
      "(20640, 8)\n",
      "(20640,)\n"
     ]
    }
   ],
   "source": [
    "from sklearn.datasets import fetch_california_housing\n",
    "\n",
    "housing = fetch_california_housing(data_home='data')\n",
    "print(housing.DESCR)\n",
    "print(housing.data.shape)\n",
    "print(housing.target.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T03:09:54.024390600Z",
     "start_time": "2024-07-18T03:09:54.006741600Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "array([[ 8.32520000e+00,  4.10000000e+01,  6.98412698e+00,\n",
      "         1.02380952e+00,  3.22000000e+02,  2.55555556e+00,\n",
      "         3.78800000e+01, -1.22230000e+02],\n",
      "       [ 8.30140000e+00,  2.10000000e+01,  6.23813708e+00,\n",
      "         9.71880492e-01,  2.40100000e+03,  2.10984183e+00,\n",
      "         3.78600000e+01, -1.22220000e+02]])\n",
      "--------------------------------------------------\n",
      "array([4.526, 3.585])\n"
     ]
    }
   ],
   "source": [
    "# print(housing.data[0:5])\n",
    "import pprint  #打印的格式比较 好看\n",
    "\n",
    "pprint.pprint(housing.data[0:2])\n",
    "print('-'*50)\n",
    "pprint.pprint(housing.target[0:2])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T03:10:11.560482300Z",
     "start_time": "2024-07-18T03:10:11.475280800Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(11610, 8) (11610,)\n",
      "(3870, 8) (3870,)\n",
      "(5160, 8) (5160,)\n"
     ]
    }
   ],
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "#拆分训练集和测试集，random_state是随机种子,同样的随机数种子，是为了得到同样的随机值\n",
    "x_train_all, x_test, y_train_all, y_test = train_test_split(\n",
    "    housing.data, housing.target, random_state = 7)\n",
    "x_train, x_valid, y_train, y_valid = train_test_split(\n",
    "    x_train_all, y_train_all, random_state = 11)\n",
    "# 训练集\n",
    "print(x_train.shape, y_train.shape)\n",
    "# 验证集\n",
    "print(x_valid.shape, y_valid.shape)\n",
    "# 测试集\n",
    "print(x_test.shape, y_test.shape)\n",
    "\n",
    "dataset_maps = {\n",
    "    \"train\": [x_train, y_train],\n",
    "    \"valid\": [x_valid, y_valid],\n",
    "    \"test\": [x_test, y_test],\n",
    "} #把3个数据集都放到字典中\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "outputs": [
    {
     "data": {
      "text/plain": "numpy.ndarray"
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "type(x_train)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-07-18T03:17:22.264621900Z",
     "start_time": "2024-07-18T03:17:22.257794900Z"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T03:16:30.331411900Z",
     "start_time": "2024-07-18T03:16:30.297939600Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": "StandardScaler()",
      "text/html": "<style>#sk-container-id-1 {\n  /* Definition of color scheme common for light and dark mode */\n  --sklearn-color-text: black;\n  --sklearn-color-line: gray;\n  /* Definition of color scheme for unfitted estimators */\n  --sklearn-color-unfitted-level-0: #fff5e6;\n  --sklearn-color-unfitted-level-1: #f6e4d2;\n  --sklearn-color-unfitted-level-2: #ffe0b3;\n  --sklearn-color-unfitted-level-3: chocolate;\n  /* Definition of color scheme for fitted estimators */\n  --sklearn-color-fitted-level-0: #f0f8ff;\n  --sklearn-color-fitted-level-1: #d4ebff;\n  --sklearn-color-fitted-level-2: #b3dbfd;\n  --sklearn-color-fitted-level-3: cornflowerblue;\n\n  /* Specific color for light theme */\n  --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n  --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, white)));\n  --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n  --sklearn-color-icon: #696969;\n\n  @media (prefers-color-scheme: dark) {\n    /* Redefinition of color scheme for dark theme */\n    --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n    --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, #111)));\n    --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n    --sklearn-color-icon: #878787;\n  }\n}\n\n#sk-container-id-1 {\n  color: var(--sklearn-color-text);\n}\n\n#sk-container-id-1 pre {\n  padding: 0;\n}\n\n#sk-container-id-1 input.sk-hidden--visually {\n  border: 0;\n  clip: rect(1px 1px 1px 1px);\n  clip: rect(1px, 1px, 1px, 1px);\n  height: 1px;\n  margin: -1px;\n  overflow: hidden;\n  padding: 0;\n  position: absolute;\n  width: 1px;\n}\n\n#sk-container-id-1 div.sk-dashed-wrapped {\n  border: 1px dashed var(--sklearn-color-line);\n  margin: 0 0.4em 0.5em 0.4em;\n  box-sizing: border-box;\n  padding-bottom: 0.4em;\n  background-color: var(--sklearn-color-background);\n}\n\n#sk-container-id-1 div.sk-container {\n  /* jupyter's `normalize.less` sets `[hidden] { display: none; }`\n     but bootstrap.min.css set `[hidden] { display: none !important; }`\n     so we also need the `!important` here to be able to override the\n     default hidden behavior on the sphinx rendered scikit-learn.org.\n     See: https://github.com/scikit-learn/scikit-learn/issues/21755 */\n  display: inline-block !important;\n  position: relative;\n}\n\n#sk-container-id-1 div.sk-text-repr-fallback {\n  display: none;\n}\n\ndiv.sk-parallel-item,\ndiv.sk-serial,\ndiv.sk-item {\n  /* draw centered vertical line to link estimators */\n  background-image: linear-gradient(var(--sklearn-color-text-on-default-background), var(--sklearn-color-text-on-default-background));\n  background-size: 2px 100%;\n  background-repeat: no-repeat;\n  background-position: center center;\n}\n\n/* Parallel-specific style estimator block */\n\n#sk-container-id-1 div.sk-parallel-item::after {\n  content: \"\";\n  width: 100%;\n  border-bottom: 2px solid var(--sklearn-color-text-on-default-background);\n  flex-grow: 1;\n}\n\n#sk-container-id-1 div.sk-parallel {\n  display: flex;\n  align-items: stretch;\n  justify-content: center;\n  background-color: var(--sklearn-color-background);\n  position: relative;\n}\n\n#sk-container-id-1 div.sk-parallel-item {\n  display: flex;\n  flex-direction: column;\n}\n\n#sk-container-id-1 div.sk-parallel-item:first-child::after {\n  align-self: flex-end;\n  width: 50%;\n}\n\n#sk-container-id-1 div.sk-parallel-item:last-child::after {\n  align-self: flex-start;\n  width: 50%;\n}\n\n#sk-container-id-1 div.sk-parallel-item:only-child::after {\n  width: 0;\n}\n\n/* Serial-specific style estimator block */\n\n#sk-container-id-1 div.sk-serial {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  background-color: var(--sklearn-color-background);\n  padding-right: 1em;\n  padding-left: 1em;\n}\n\n\n/* Toggleable style: style used for estimator/Pipeline/ColumnTransformer box that is\nclickable and can be expanded/collapsed.\n- Pipeline and ColumnTransformer use this feature and define the default style\n- Estimators will overwrite some part of the style using the `sk-estimator` class\n*/\n\n/* Pipeline and ColumnTransformer style (default) */\n\n#sk-container-id-1 div.sk-toggleable {\n  /* Default theme specific background. It is overwritten whether we have a\n  specific estimator or a Pipeline/ColumnTransformer */\n  background-color: var(--sklearn-color-background);\n}\n\n/* Toggleable label */\n#sk-container-id-1 label.sk-toggleable__label {\n  cursor: pointer;\n  display: block;\n  width: 100%;\n  margin-bottom: 0;\n  padding: 0.5em;\n  box-sizing: border-box;\n  text-align: center;\n}\n\n#sk-container-id-1 label.sk-toggleable__label-arrow:before {\n  /* Arrow on the left of the label */\n  content: \"▸\";\n  float: left;\n  margin-right: 0.25em;\n  color: var(--sklearn-color-icon);\n}\n\n#sk-container-id-1 label.sk-toggleable__label-arrow:hover:before {\n  color: var(--sklearn-color-text);\n}\n\n/* Toggleable content - dropdown */\n\n#sk-container-id-1 div.sk-toggleable__content {\n  max-height: 0;\n  max-width: 0;\n  overflow: hidden;\n  text-align: left;\n  /* unfitted */\n  background-color: var(--sklearn-color-unfitted-level-0);\n}\n\n#sk-container-id-1 div.sk-toggleable__content.fitted {\n  /* fitted */\n  background-color: var(--sklearn-color-fitted-level-0);\n}\n\n#sk-container-id-1 div.sk-toggleable__content pre {\n  margin: 0.2em;\n  border-radius: 0.25em;\n  color: var(--sklearn-color-text);\n  /* unfitted */\n  background-color: var(--sklearn-color-unfitted-level-0);\n}\n\n#sk-container-id-1 div.sk-toggleable__content.fitted pre {\n  /* unfitted */\n  background-color: var(--sklearn-color-fitted-level-0);\n}\n\n#sk-container-id-1 input.sk-toggleable__control:checked~div.sk-toggleable__content {\n  /* Expand drop-down */\n  max-height: 200px;\n  max-width: 100%;\n  overflow: auto;\n}\n\n#sk-container-id-1 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {\n  content: \"▾\";\n}\n\n/* Pipeline/ColumnTransformer-specific style */\n\n#sk-container-id-1 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {\n  color: var(--sklearn-color-text);\n  background-color: var(--sklearn-color-unfitted-level-2);\n}\n\n#sk-container-id-1 div.sk-label.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n  background-color: var(--sklearn-color-fitted-level-2);\n}\n\n/* Estimator-specific style */\n\n/* Colorize estimator box */\n#sk-container-id-1 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {\n  /* unfitted */\n  background-color: var(--sklearn-color-unfitted-level-2);\n}\n\n#sk-container-id-1 div.sk-estimator.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n  /* fitted */\n  background-color: var(--sklearn-color-fitted-level-2);\n}\n\n#sk-container-id-1 div.sk-label label.sk-toggleable__label,\n#sk-container-id-1 div.sk-label label {\n  /* The background is the default theme color */\n  color: var(--sklearn-color-text-on-default-background);\n}\n\n/* On hover, darken the color of the background */\n#sk-container-id-1 div.sk-label:hover label.sk-toggleable__label {\n  color: var(--sklearn-color-text);\n  background-color: var(--sklearn-color-unfitted-level-2);\n}\n\n/* Label box, darken color on hover, fitted */\n#sk-container-id-1 div.sk-label.fitted:hover label.sk-toggleable__label.fitted {\n  color: var(--sklearn-color-text);\n  background-color: var(--sklearn-color-fitted-level-2);\n}\n\n/* Estimator label */\n\n#sk-container-id-1 div.sk-label label {\n  font-family: monospace;\n  font-weight: bold;\n  display: inline-block;\n  line-height: 1.2em;\n}\n\n#sk-container-id-1 div.sk-label-container {\n  text-align: center;\n}\n\n/* Estimator-specific */\n#sk-container-id-1 div.sk-estimator {\n  font-family: monospace;\n  border: 1px dotted var(--sklearn-color-border-box);\n  border-radius: 0.25em;\n  box-sizing: border-box;\n  margin-bottom: 0.5em;\n  /* unfitted */\n  background-color: var(--sklearn-color-unfitted-level-0);\n}\n\n#sk-container-id-1 div.sk-estimator.fitted {\n  /* fitted */\n  background-color: var(--sklearn-color-fitted-level-0);\n}\n\n/* on hover */\n#sk-container-id-1 div.sk-estimator:hover {\n  /* unfitted */\n  background-color: var(--sklearn-color-unfitted-level-2);\n}\n\n#sk-container-id-1 div.sk-estimator.fitted:hover {\n  /* fitted */\n  background-color: var(--sklearn-color-fitted-level-2);\n}\n\n/* Specification for estimator info (e.g. \"i\" and \"?\") */\n\n/* Common style for \"i\" and \"?\" */\n\n.sk-estimator-doc-link,\na:link.sk-estimator-doc-link,\na:visited.sk-estimator-doc-link {\n  float: right;\n  font-size: smaller;\n  line-height: 1em;\n  font-family: monospace;\n  background-color: var(--sklearn-color-background);\n  border-radius: 1em;\n  height: 1em;\n  width: 1em;\n  text-decoration: none !important;\n  margin-left: 1ex;\n  /* unfitted */\n  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n  color: var(--sklearn-color-unfitted-level-1);\n}\n\n.sk-estimator-doc-link.fitted,\na:link.sk-estimator-doc-link.fitted,\na:visited.sk-estimator-doc-link.fitted {\n  /* fitted */\n  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n  color: var(--sklearn-color-fitted-level-1);\n}\n\n/* On hover */\ndiv.sk-estimator:hover .sk-estimator-doc-link:hover,\n.sk-estimator-doc-link:hover,\ndiv.sk-label-container:hover .sk-estimator-doc-link:hover,\n.sk-estimator-doc-link:hover {\n  /* unfitted */\n  background-color: var(--sklearn-color-unfitted-level-3);\n  color: var(--sklearn-color-background);\n  text-decoration: none;\n}\n\ndiv.sk-estimator.fitted:hover .sk-estimator-doc-link.fitted:hover,\n.sk-estimator-doc-link.fitted:hover,\ndiv.sk-label-container:hover .sk-estimator-doc-link.fitted:hover,\n.sk-estimator-doc-link.fitted:hover {\n  /* fitted */\n  background-color: var(--sklearn-color-fitted-level-3);\n  color: var(--sklearn-color-background);\n  text-decoration: none;\n}\n\n/* Span, style for the box shown on hovering the info icon */\n.sk-estimator-doc-link span {\n  display: none;\n  z-index: 9999;\n  position: relative;\n  font-weight: normal;\n  right: .2ex;\n  padding: .5ex;\n  margin: .5ex;\n  width: min-content;\n  min-width: 20ex;\n  max-width: 50ex;\n  color: var(--sklearn-color-text);\n  box-shadow: 2pt 2pt 4pt #999;\n  /* unfitted */\n  background: var(--sklearn-color-unfitted-level-0);\n  border: .5pt solid var(--sklearn-color-unfitted-level-3);\n}\n\n.sk-estimator-doc-link.fitted span {\n  /* fitted */\n  background: var(--sklearn-color-fitted-level-0);\n  border: var(--sklearn-color-fitted-level-3);\n}\n\n.sk-estimator-doc-link:hover span {\n  display: block;\n}\n\n/* \"?\"-specific style due to the `<a>` HTML tag */\n\n#sk-container-id-1 a.estimator_doc_link {\n  float: right;\n  font-size: 1rem;\n  line-height: 1em;\n  font-family: monospace;\n  background-color: var(--sklearn-color-background);\n  border-radius: 1rem;\n  height: 1rem;\n  width: 1rem;\n  text-decoration: none;\n  /* unfitted */\n  color: var(--sklearn-color-unfitted-level-1);\n  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n}\n\n#sk-container-id-1 a.estimator_doc_link.fitted {\n  /* fitted */\n  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n  color: var(--sklearn-color-fitted-level-1);\n}\n\n/* On hover */\n#sk-container-id-1 a.estimator_doc_link:hover {\n  /* unfitted */\n  background-color: var(--sklearn-color-unfitted-level-3);\n  color: var(--sklearn-color-background);\n  text-decoration: none;\n}\n\n#sk-container-id-1 a.estimator_doc_link.fitted:hover {\n  /* fitted */\n  background-color: var(--sklearn-color-fitted-level-3);\n}\n</style><div id=\"sk-container-id-1\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>StandardScaler()</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item\"><div class=\"sk-estimator fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-1\" type=\"checkbox\" checked><label for=\"sk-estimator-id-1\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow fitted\">&nbsp;&nbsp;StandardScaler<a class=\"sk-estimator-doc-link fitted\" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.5/modules/generated/sklearn.preprocessing.StandardScaler.html\">?<span>Documentation for StandardScaler</span></a><span class=\"sk-estimator-doc-link fitted\">i<span>Fitted</span></span></label><div class=\"sk-toggleable__content fitted\"><pre>StandardScaler()</pre></div> </div></div></div></div>"
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from sklearn.preprocessing import StandardScaler\n",
    "\n",
    "\n",
    "\n",
    "scaler = StandardScaler() #标准化\n",
    "scaler.fit(x_train) #fit和fit_transform的区别，fit是计算均值和方差，fit_transform是先fit，然后transform"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "outputs": [
    {
     "data": {
      "text/plain": "()"
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.array(1).shape"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-07-18T03:24:29.260017700Z",
     "start_time": "2024-07-18T03:24:29.252881600Z"
    }
   }
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 构建数据集"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T03:29:08.973115500Z",
     "start_time": "2024-07-18T03:29:08.960843100Z"
    }
   },
   "outputs": [],
   "source": [
    "#构建私有数据集，就需要用如下方式\n",
    "from torch.utils.data import Dataset\n",
    "\n",
    "class HousingDataset(Dataset):\n",
    "    def __init__(self, mode='train'):\n",
    "        self.x, self.y = dataset_maps[mode] #x,y都是ndarray类型\n",
    "        self.x = torch.from_numpy(scaler.transform(self.x)).float() #from_numpy将NumPy数组转换成PyTorch张量\n",
    "        self.y = torch.from_numpy(self.y).float().reshape(-1, 1) #处理为多行1列的tensor类型\n",
    "            \n",
    "    def __len__(self): #必须写\n",
    "        return len(self.x) #返回数据集的长度,0维的size\n",
    "    \n",
    "    def __getitem__(self, idx): #idx是索引，返回的是数据和标签，这里是一个样本\n",
    "        return self.x[idx], self.y[idx]\n",
    "    \n",
    "#train_ds是dataset类型的数据，与前面例子的FashionMNIST类型一致\n",
    "train_ds = HousingDataset(\"train\")\n",
    "valid_ds = HousingDataset(\"valid\")\n",
    "test_ds = HousingDataset(\"test\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T03:29:15.953307900Z",
     "start_time": "2024-07-18T03:29:15.945585400Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": "(tensor([ 0.8015,  0.2722, -0.1162, -0.2023, -0.5431, -0.0210, -0.5898, -0.0824]),\n tensor([3.2260]))"
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "train_ds[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "outputs": [
    {
     "data": {
      "text/plain": "torch.Size([8])"
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "train_ds[0][0]"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-07-18T03:29:48.932161500Z",
     "start_time": "2024-07-18T03:29:48.925274400Z"
    }
   }
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### DataLoader"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T03:30:18.978489900Z",
     "start_time": "2024-07-18T03:30:18.968425200Z"
    }
   },
   "outputs": [],
   "source": [
    "from torch.utils.data import DataLoader\n",
    "\n",
    "#放到DataLoader中的train_ds, valid_ds, test_ds都是dataset类型的数据\n",
    "batch_size = 16\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True)\n",
    "val_loader = DataLoader(valid_ds, batch_size=batch_size, shuffle=False)\n",
    "test_loader = DataLoader(test_ds, batch_size=batch_size, shuffle=False)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T03:35:19.886022500Z",
     "start_time": "2024-07-18T03:35:19.882013400Z"
    }
   },
   "outputs": [],
   "source": [
    "#回归模型我们只需要1个数\n",
    "\n",
    "class NeuralNetwork(nn.Module):\n",
    "    def __init__(self, input_dim=8):\n",
    "        super().__init__()\n",
    "        self.linear_relu_stack = nn.Sequential(\n",
    "            nn.Linear(input_dim, 30),\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(30, 1)\n",
    "            )\n",
    "        \n",
    "    def forward(self, x):\n",
    "        # x.shape [batch size, 8]\n",
    "        logits = self.linear_relu_stack(x)\n",
    "        # logits.shape [batch size, 1]\n",
    "        return logits"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T03:35:23.265092800Z",
     "start_time": "2024-07-18T03:35:23.238971400Z"
    }
   },
   "outputs": [],
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "        \n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else: \n",
    "            self.counter += 1\n",
    "            \n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T03:35:36.376447200Z",
     "start_time": "2024-07-18T03:35:36.353553600Z"
    }
   },
   "outputs": [],
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "        \n",
    "    return np.mean(loss_list)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T03:40:09.485531200Z",
     "start_time": "2024-07-18T03:38:00.276086800Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": "  0%|          | 0/72600 [00:00<?, ?it/s]",
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "b92b858ccf944008a28c97c5dda18ee5"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Early stop at epoch 63 / global_step 45738\n"
     ]
    }
   ],
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "    \n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:#11610/16=725\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas) #得到预测值\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    " \n",
    "                loss = loss.cpu().item() #转为cpu类型，item()是取值\n",
    "                # record\n",
    "                \n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"step\": global_step\n",
    "                })\n",
    "                \n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "\n",
    "                    # 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(-val_loss) #根据验证集的损失来实现早停\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "                    \n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "        \n",
    "    return record_dict\n",
    "        \n",
    "\n",
    "epoch = 100\n",
    "\n",
    "model = NeuralNetwork()\n",
    "\n",
    "# 1. 定义损失函数 采用MSE损失,均方误差\n",
    "loss_fct = nn.MSELoss()\n",
    "# 2. 定义优化器 采用SGD\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)\n",
    "\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=10, min_delta=1e-3)\n",
    "\n",
    "model = model.to(device)\n",
    "record = training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "record"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T03:41:09.033660300Z",
     "start_time": "2024-07-18T03:41:08.799715800Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": "<Figure size 640x480 with 1 Axes>",
      "image/png": "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GCyIiIj8T3UrbzoYLIiIiAKmWdNN61q9erUPiuFDrq4QmcGCiIjI31TbYqFliwUREZHfqS5YQOtqseACKERERP6mwmChTDfVscWCiIjI79QXLJxjLLQcY0FEROR3qgsWrummDBZERET+p7pgIWid003hAOy2AJeGiIjoyqK6YAFtneXD7ZbGjyMiIiKfU12wEOsGCxuDBRERkT+pMFjoYJOdvxaDBRERkV+pLlhoRAFWOHdgs9UEtjBERERXGFUGC4srWNg5M4SIiMifVBksrK4tUNhiQURE5FeqCxZaUYBFdnWFsMWCiIjIn1QXLDSiCAuU1TfZYkFERORfKgwWqO0K4ToWREREfqXCYFFn8CanmxIREfmV6oKFVhTrTDdlsCAiIvIn1QULjcfgTQYLIiIif1JdsNDWXSCLYyyIiIj8SnXBQhQ4xoKIiChQVBcstKIAi3uBLAYLIiIif1JdsOAYCyIiosBRX7DQcIwFERFRoKguWGg91rHgyptERET+pLpgIQp1t03nXiFERET+pLpg4bkJGVssiIiI/El1waLutukyB28SERH5leqChTLGQtndlMGCiIjIv1QXLES2WBAREQWM6oJF3TEWssQxFkRERP6kumChqbtXCFssiIiI/Ep9wYJ7hRAREQWM6oKFKAqQnMFC5sqbREREfqW6YAEAkqjMCuE6FkRERP6lymBhF5QWC4ErbxIREfmVV8HivffeQ58+fWA2m2E2m5Geno6lS5e2VtmazSYalP+wK4SIiMivvAoWSUlJeOWVV7Bjxw5s374d119/PSZNmoT9+/e3VvmapbbFgl0hRERE/qT15uCJEyd6fP3SSy/hvffew+bNm9GrV68Gf8ZiscBiqW05KCsrAwBIkgRJkrwtb6Nc55IkCTZRDzgAwW716WNQ09StCwoc1kPwYF0EB9ZDyzT1unkVLOqy2+346quvUFlZifT09EaPmzNnDmbPnl3v/hUrVsBkMjX34RuVkZGBakkGNABsNViyZInPH4OaJiMjI9BFILAeggnrIjiwHpqnqqqqSccJsizL3px47969SE9PR01NDcLCwvD5559jwoQJjR7fUItFcnIyioqKYDabvXnoi5IkCRkZGRgzZgxu+edyrJCmKvfPzAfEZucnaoa6daHT6QJdnCsW6yF4sC6CA+uhZcrKyhAbG4vS0tKLvn97/Y7b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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        plt.plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        plt.plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        plt.grid()\n",
    "        plt.legend()\n",
    "        # plt.xticks(range(0, train_df.index[-1], 10*sample_step), range(0, train_df.index[-1], 10*sample_step))\n",
    "        plt.xlabel(\"step\")\n",
    "\n",
    "        plt.show()\n",
    "\n",
    "plot_learning_curves(record)  #横坐标是 steps"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 测试集"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T03:41:12.191677100Z",
     "start_time": "2024-07-18T03:41:12.105840300Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.3176\n"
     ]
    }
   ],
   "source": [
    "model.eval()\n",
    "loss = evaluating(model, val_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "pytorch",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.8"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
